package com.lyf.boot.web.filter;

import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.lyf.boot.core.model.R;
import com.lyf.boot.exception.ServiceException;
import com.lyf.boot.properties.AuthProperties;
import com.lyf.boot.utils.PathMatchUtil;
import com.lyf.boot.utils.ServerUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Luyufan
 * @date 2023/7/3 13:36
 */
@Slf4j
public class AuthFilter implements Filter {

    private final static String PARAM_SIGN = "sign";

    private AuthProperties authProperties;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        authProperties = SpringUtil.getBean(AuthProperties.class);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = AuthHttpServletRequestWrapper.getInstance(servletRequest);
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String url = request.getRequestURI();
        log.debug("requestPath: {}", url);
        Map<String, String[]> paramsMap = request.getParameterMap();
        String queryParams = CollUtil.isEmpty(paramsMap) ? StrUtil.EMPTY : JSONUtil.toJsonStr(paramsMap);
        log.debug("query params: [{}]", queryParams);
        // 跳过 文件上传 的字节流
        String type = StrUtil.isNotBlank(request.getContentType()) ? request.getContentType() : "";
        String bodyParams = type;
        if (!type.contains(ContentType.MULTIPART.getValue())) {
            bodyParams = IoUtil.read(request.getInputStream(), StandardCharsets.UTF_8);
        }
        //将换行符替换为空字符串
        log.debug("body params: [{}]", bodyParams.replace("\r", "").replace("\n", ""));
        String paramSign = request.getHeader(PARAM_SIGN);
        log.debug("sign params: [{}]", StrUtil.blankToDefault(paramSign,StrUtil.EMPTY));
        String remoteAddr = request.getRemoteAddr();
        String ip = ServerUtil.clientIp();
//        String ipLocation = ServerUtil.getIpRegion(ip);
//        log.debug("remoteAddr: [{}],ip: [{}], ip location: [{}]", remoteAddr, ip, ipLocation);
        String authorization = request.getHeader("Authorization");
        log.debug("Authorization: [{}]", StrUtil.blankToDefault(authorization,StrUtil.EMPTY));
        String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin);
        String methods = request.getMethod();
        response.setHeader("Access-Control-Allow-Methods", methods);
        String contentType = request.getContentType();
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,XFILENAME,XFILECATEGORY,XFILESIZE");
        String headers = request.getHeader("Access-Control-Request-Headers");
        response.setHeader("Access-Control-Allow-Headers", headers);
        log.debug("Origin: [{}],Methods: [{}],Content-Type: [{}],headers: [{}]", origin, methods, contentType, headers);
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (methods.equals(RequestMethod.OPTIONS.name())) {
            response.setStatus(HttpStatus.OK.value());
            return;
        }
        signIntercept(url, bodyParams, paramSign, request, response, filterChain);
    }

    private void signIntercept(String url, String bodyParams, String paramSign, HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        String contentType = StrUtil.isBlank(request.getContentType()) ? "" : request.getContentType();
        // 是否验签
        if (!contentType.contains(ContentType.MULTIPART.getValue())) {
            if (PathMatchUtil.isNotMatch(url, authProperties.getSignWhiteList())) {
                // 校验签名
                Map<String, Object> map = MapUtil.newHashMap(1);
                if (StrUtil.isNotBlank(bodyParams)) {
                    map = JSONUtil.parseObj(bodyParams);
                }
                String sign = getSign(map, authProperties.getEncryptKey());
                log.debug("target sign: [{}]", sign);
                if (!StrUtil.equals(paramSign, sign)) {
                    log.error("接口签名错误");
                    ServerUtil.write(response,R.fail(402, "签名错误"));
                    return;
                }
            }
        }
        tokenIntercept(url, request, response, filterChain);
    }

    /**
     * Token Intercept
     */
    private void tokenIntercept(String url, HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        try {
            // 是否在白名单
            if (PathMatchUtil.isNotMatch(url, authProperties.getApiWhiteList())) {
                //StpUtil.checkLogin();
                //StpUtil.renewTimeout(SaManager.getConfig().getTimeout());
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            R<?> result;
            // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
            Throwable throwable = e.getCause();
            if (null == throwable) {
                throwable = e;
            }
            if (throwable instanceof NotLoginException) {
                NotLoginException exception = (NotLoginException) throwable;
                String msg = StrUtil.isBlank(exception.getMessage()) ? "请先登录" : exception.getMessage().split("：")[0].replace("Token", "令牌");
                result = R.fail(401, msg);
                log.error(exception.getMessage(), throwable);
            } else if (throwable instanceof ServletException) {
                ServiceException exception = (ServiceException) throwable;
                result = R.fail(exception.getCode(), exception.getMessage());
            } else {
                result = R.fail("系统异常");
                log.error("系统异常", throwable);
            }
            ServerUtil.write(response, result);
        }
    }

    private static String getSign(Map<String, Object> map, String signKey) {
        try {
            map.put("signKey", signKey);
            List<String> list = new ArrayList<>(map.size());
            map.forEach((key, value) -> list.add(key));
            List<String> signList = new ArrayList<>();
            // 字符串排序
            CollUtil.sort(list, String::compareTo);
            list.forEach(key -> {
                if (null != map.get(key)) {
                    String v = String.valueOf(map.get(key));
                    if(StrUtil.isNotBlank(v)){
                        signList.add(key + "=" + v);
                    }
                }
            });
            String sb = CollUtil.join(signList, "&");
            return SecureUtil.sha256(sb);
        } catch (Exception e) {
            log.error("sign fail", e);
            throw e;
        }
    }
}
